#pragma once

#include <vector>
#include "domdef.h"

#define INVALID_COOKIE ((DWORD)-1)

template<class T>
class EventListenerImpl : public IDispatch
{
	//friend typename T;
private:
	LONG ulRef;

	//class EventListenerMgr : public std::vector<EventListenerImpl<T>*>
	//{
	//public:
	//	void forget(EventListenerImpl<T>* p)
	//	{
	//		for (iterator itr = begin(); itr!=end(); itr++)
	//		{
	//			if (*itr == p)
	//			{
	//				erase(itr);
	//				return;
	//			}
	//		}
	//	}
	//};
	//static EventListenerMgr Mgr;

//public:
protected:
	DWORD cookie;
	//const IID* piid;
	IID iid;
	IUnknown* pUnk;

	typedef map<DISPID, CComBSTR> EventsCache;
	EventsCache evt_cache;

	bool has_name(LPCOLESTR name)
	{
		for (EventsCache::iterator itr=evt_cache.begin(); itr!=evt_cache.end(); itr++)
		{
			if ((*itr).second == name)
				return true;
		}
		return false;
	}

	void cache_events()
	{
		HRESULT hr = E_FAIL;

		CComQIPtr<IDispatch> spDisp = pUnk;
		CComPtr<ITypeInfo> spTypeInfo;
		hr = spDisp->GetTypeInfo(0, 0, &spTypeInfo);
		if (SUCCEEDED(hr))
		{
			CComPtr<ITypeLib> spTypeLib;
			hr = spTypeInfo->GetContainingTypeLib(&spTypeLib, 0);
			if (SUCCEEDED(hr))
			{
				CComPtr<ITypeInfo> ti;
				hr = spTypeLib->GetTypeInfoOfGuid(iid, &ti);
				if (SUCCEEDED(hr))
				{
					TYPEATTR* attr=NULL;
					hr = ti->GetTypeAttr(&attr);
					if (SUCCEEDED(hr))
					{
						for (WORD i=0; i<attr->cFuncs; i++)
						{
							FUNCDESC* fd =NULL;
							ti->GetFuncDesc(i, &fd);
							if (fd)
							{
								CComBSTR name;
								ti->GetDocumentation(fd->memid, &name, NULL, NULL, NULL);
								evt_cache[fd->memid] = name;
								ti->ReleaseFuncDesc(fd);
								fd = NULL;
							}
						}
						ti->ReleaseTypeAttr(attr);
					}
				}
			}
		}
	}

	HRESULT GetSourceIID(IUnknown* unk, IID* piid)
	{
		HRESULT hr = E_FAIL;
		bool found = false;

		CComQIPtr<IDispatch> spDisp = pUnk;
		CComPtr<ITypeInfo> spTypeInfo;
		hr = spDisp->GetTypeInfo(0, 0, &spTypeInfo);
		if (SUCCEEDED(hr))
		{
			CComPtr<ITypeLib> spTypeLib;
			hr = spTypeInfo->GetContainingTypeLib(&spTypeLib, 0);
			if (SUCCEEDED(hr))
			{
				UINT num = spTypeLib->GetTypeInfoCount();
				for (UINT i=0; i<num; i++)
				{
					TYPEKIND tk;
					hr = spTypeLib->GetTypeInfoType(i, &tk);
					CComPtr<ITypeInfo> ti;
					hr = spTypeLib->GetTypeInfo(i, &ti);
					if (SUCCEEDED(hr))
					{
						TYPEATTR* attr=NULL;
						hr = ti->GetTypeAttr(&attr);
						if (SUCCEEDED(hr))
						{
							HREFTYPE hRef;
							for (WORD j=0; j<attr->cImplTypes; j++)
							{
								int nType;
								hr = ti->GetImplTypeFlags(j, &nType);
								if (SUCCEEDED(hr))
								{
									if (nType == (IMPLTYPEFLAG_FDEFAULT | IMPLTYPEFLAG_FSOURCE))
									{
										// found!!
										found = true;
										hr = ti->GetRefTypeOfImplType(j, &hRef);
										if (SUCCEEDED(hr))
										{
											CComPtr<ITypeInfo> ti2;
											hr = ti->GetRefTypeInfo(hRef, &ti2);
											if (SUCCEEDED(hr))
											{
												TYPEATTR* pAttrIF;
												ti2->GetTypeAttr(&pAttrIF);
												if (pAttrIF != NULL)
												{
													Checked::memcpy_s(piid, sizeof(GUID), &pAttrIF->guid, sizeof(GUID));
													ti2->ReleaseTypeAttr(pAttrIF);
												}
											}
										}
										break;
									}
								}
							}
							ti->ReleaseTypeAttr(attr);
						}
					}
					if (found) break;
				}
			}
		}

		if (!found)
		{
			CComQIPtr<IConnectionPointContainer> cpc = unk;
			if (cpc)
			{
				CComPtr<IEnumConnectionPoints> ecp;
				hr = cpc->EnumConnectionPoints(&ecp);
				if (SUCCEEDED(hr))
				{
					CComPtr<IConnectionPoint> cp;
					hr = ecp->Next(1, &cp, NULL);
					if (SUCCEEDED(hr))
					{
						return cp->GetConnectionInterface(piid);
					}
				}
			}
		}
		return found ? S_OK : E_FAIL;
	}

public:
	EventListenerImpl() : pUnk(NULL), /*piid(get_event_iid(unk)),*/ ulRef(0), cookie(INVALID_COOKIE)
	{
	}

	virtual ~EventListenerImpl()
	{
		disconnect();
	}

	void connect(IUnknown* unk)
	{
		return;
		if (is_connected())
			return;

		if (unk)
		{
			pUnk = unk;
			pUnk->AddRef();
			if (cookie==INVALID_COOKIE)
			{
				GUID libid;
				WORD wMajor, wMinor;
				HRESULT hr = AtlGetObjectSourceInterface(pUnk, &libid, &iid, &wMajor, &wMinor);
				if (FAILED(hr))
					hr = GetSourceIID(pUnk, &iid);
				if (SUCCEEDED(hr))
				{
					AtlAdvise(pUnk, (IDispatch*)this, iid, &cookie);
					cache_events();
				}
				//Mgr.push_back(this);
			}
		}
	}
	void disconnect()
	{
		if (pUnk==NULL) return;
		if (cookie!=INVALID_COOKIE)
		{
			AtlUnadvise(pUnk, iid, cookie);
			evt_cache.clear();
		}
		pUnk->Release();
		pUnk = NULL;
		cookie = INVALID_COOKIE;
	}
	inline bool is_connected() { return pUnk && cookie!=INVALID_COOKIE; }
	//static T* New(IUnknown* unk)
	//{
	//	if (unk == NULL)
	//		return NULL;
	//	return new T(unk);
	//}
	//static void ClearAll()
	//{
	//	for (EventListenerMgr::iterator itr=Mgr.begin(); itr!=Mgr.end(); itr++)
	//	{
	//		EventListenerImpl<T>* p = *itr;
	//		if (p)
	//			delete static_cast<T*>(p);
	//	}
	//}

	STDMETHOD_(ULONG, AddRef)() { return 2; }
	//{
	//	ULONG ref = InterlockedIncrement((LONG*)&ulRef);
	//	return ref;
	//}
	STDMETHOD_(ULONG, Release)() { return 1; }
	//{
	//	ULONG ref = InterlockedDecrement((LONG*)&ulRef);
	//	if (ref == 0)
	//	{
	//		ATLTRACE(L"EventListenerImpl is deleted.\n");
	//		//Mgr.forget(this);
	//		delete static_cast<T*>(this);
	//	}
	//	return ref;
	//}
	STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj)
	{
		if (!ppvObj)
			return E_POINTER;

		*ppvObj = NULL;
		if (IsEqualIID(iid, __uuidof(IUnknown)) ||
			IsEqualIID(iid, __uuidof(IDispatch)))
			*ppvObj = this;
		if (*ppvObj)
		{
			((LPUNKNOWN)(*ppvObj))->AddRef();
			return S_OK;
		}
		return E_NOINTERFACE;
	}
	STDMETHOD(GetTypeInfoCount)(UINT *pctinfo)
	{
		*pctinfo = 0;
		ATLTRACENOTIMPL(_T("EventListenerImpl::GetTypeInfoCount"));
	}
	STDMETHOD(GetTypeInfo)(UINT /*iTInfo*/, LCID /*lcid*/, ITypeInfo **ppTInfo)
	{
		*ppTInfo = NULL;
		ATLTRACENOTIMPL(_T("EventListenerImpl::GetTypeInfo"));
	}
	STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR ** rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId)
	{
		ATLTRACENOTIMPL(_T("EventListenerImpl::GetIDsOfNames"));
	}
	STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT *pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr)
	{
		static_cast<T*>(this)->OnFiredEvent(dispIdMember);
		return S_OK;
	}
};
//template<class T> __declspec(selectany) typename EventListenerImpl<T>::EventListenerMgr EventListenerImpl<T>::Mgr;

